const isProxy = Symbol("isProxy");
const MountPropertyName = "__WatchGroups__";
function checkMount(target: any, mountName: string, defaultValue: any) {
    if(target.constructor[mountName] == undefined) {
        Object.defineProperty(target.constructor, mountName, {
            value: defaultValue
        });
    }
}
function setValueProxy(val: any, callback: (target: any, key: string|number, value: any) => void) {
    let type = Object.prototype.toString.call(val);
    if(type == '[object Object]') {
        for(let key in val) {
            val[key] = setValueProxy(val[key], callback);
        }
        return new Proxy(val, {
            get: (target, idx: string) => {
                if(<any>idx == isProxy) {
                    return true;
                }
                return target[idx];
            },
            set: (target, idx: string, value: any) => {
                if(value == null || value == undefined || value[isProxy] == true) {
                    target[idx] = value;
                } else {
                    target[idx] = setValueProxy(value, callback);
                }
                callback(target, idx, value);
                return true;
            }
        });
    }
    if(type == '[object Array]') {
        for(let i = 0; i < val; i++) {
            try {
                if(val[i][isProxy] == false) {
                    val[i] = setValueProxy(val[i], callback);
                }                    
            } catch (error) {

            }
        }
        return new Proxy(val, {
            get: (target, idx: string) => {
                if(<any>idx == isProxy) {
                    return true;
                }
                return target[idx];
            },
            set: (target, idx: string, value: any) => {
                if(value[isProxy] == true) {
                    target[idx] = value;
                } else {
                    target[idx] = setValueProxy(value, callback);
                }
                if(<any>idx != 'length') {
                    callback(target, idx, value);
                }
                return true;
            }
        });
    }
    return val;
}
export function Watch(group: string, initNeedTrigger = false) {
    let isInit = !initNeedTrigger;
    return function(target: any, propertyKey: string) {
        checkMount(target, MountPropertyName, {});
        if(target.constructor[MountPropertyName].hasOwnProperty(group) == false) {
            target.constructor[MountPropertyName][group] = [];
        }
        let _value: any = undefined;
        const descriptor: PropertyDescriptor = {
            get: () => {
                return _value;
            },
            set: function(value) {
                _value = setValueProxy(value, (target1, idx: string|number, val: any) => {
                    for(let funcName of target.constructor[MountPropertyName][group]) {
                        (<any>this)[funcName].apply(this,[target1, idx, val]);
                    }
                });
                if(isInit == true) {
                    isInit = false;
                    return;
                }
                for(let funcName of target.constructor[MountPropertyName][group]) {
                    (<any>this)[funcName].apply(this,[target, propertyKey, value]);
                }
            }
        };
        return <any>descriptor;
    }
}
export function Reference(group: string) {
    return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        checkMount(target, MountPropertyName, {});
        if(target.constructor[MountPropertyName].hasOwnProperty(group) == false) {
            target.constructor[MountPropertyName][group] = [];
        }
        target.constructor[MountPropertyName][group].push(propertyKey);
    }
}